﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using ESpace;

namespace AMVCommander
{

    public partial class FormParseFromDir : Form
    {
        Dictionary<string, List<string>> DuplL = new Dictionary<string, List<string>>();

        // статистические данные
        public int cntAdd = 0;
        public int cntUpdate = 0;
        public int cntNotExist = 0;
        public int cntDupl = 0;

        string LocalPathForShow = "";

        int val = 0;

        object locker = new object();

        enum Status { None, ScanFiles, ScanFilesEnd, Load, LoadEnd, Pause, Break, BreakEnd }
        Status stat;
        List<String> fl = new List<string>();
        Thread th;

        int cnt = 0;
        bool bClose = false;

        AMVCollection oldDB;

        public FormParseFromDir()
        {
            InitializeComponent();
        }

        /// <summary>только добавление новых файлов</summary>
        bool OnlyNew;
        /// <summary>проверка только вновь существующих файлов</summary>
        bool CheckNotEqual;
        string TagForAdd;

        #region *** для логов *********************************

        List<string> Log_Add = new List<string>();
        List<string> Log_Update = new List<string>();
        List<string> Log_Duplicate = new List<string>();

        #endregion *** для логов *********************************

        public void ShowDialog(bool OnlyNew)
        {
            WindowState = FormWindowState.Normal;
            progressBar1.Value = 0;
            val = 0;
            Text = "Обновление базы";
            progressBar1.Enabled = false;

            stat = Status.None;
            fl.Clear();
            th = null;
            cntDupl = 0;

            bClose = false;

            oldDB = new AMVCollection(Gl.fPathDb);
            this.OnlyNew = OnlyNew;
            this.CheckNotEqual = false;
            TagForAdd = DateTime.Now.ToString("yyyy_MM_dd HH:mm");

            ShowDialog();
        }

        /// <summary>Обновление вновь найденых файлов для записей</summary>
        public void ShowDialogCheckNotEqual()
        {
            WindowState = FormWindowState.Normal;
            progressBar1.Value = 0;
            val = 0;
            Text = "Обновление базы";
            progressBar1.Enabled = false;

            stat = Status.None;
            fl.Clear();
            th = null;
            cntDupl = 0;

            bClose = false;

            oldDB = new AMVCollection(Gl.fPathDb);
            this.OnlyNew = false;
            this.CheckNotEqual = true;
            TagForAdd = DateTime.Now.ToString("yyyy_MM_dd HH:mm");

            // заполняем список файлов и помечаем записи, которые у которых файл найден вновь как файл несуществует для проверки файла вновь
            for (int i = 0; i < Gl.DB.Count; i++)
                if (Gl.DB[i].Stat == AMV.Status.NotEqual)
                {
                    Gl.DB[i].Stat = AMV.Status.NotExist;
                    fl.Add(Gl.DB[i].FullPath);
                }

            ShowDialog();
        }

        private void FormLoad_Shown(object sender, EventArgs e)
        {
            // если полный поиск, сначала загрузка списка. после сразу запустится StartParse()
            if (!CheckNotEqual)
            {
                stat = Status.ScanFiles;
                th = new Thread(new ThreadStart(ScanFileInTread));
                th.Start();
                timer1.Start();
            }
            // если обновление только вновь существующих файлов, зразу запуск StartParse()
            else
            {
                StartParse();
            }
        }

        void StartParse()
        {
            stat = Status.Load;
            progressBar1.Enabled = true;
            progressBar1.Value = 0;
            progressBar1.Maximum = fl.Count + 1;
            cntAdd = 0;
            th = new Thread(new ThreadStart(FillAmvInTread));
            th.Start();
            timer1.Start();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            timer1.Stop();
            if (stat == Status.ScanFiles) // сканирование файлов
            {
                if (cnt % 5 == 0)
                {
                    Text = "Сканирование файлов ";
                    if (cnt % 20 < 5) Text += "|";
                    else if (cnt % 20 < 10) Text += "/";
                    else if (cnt % 20 < 15) Text += "--";
                    else Text += "\\";
                    label1.Text = Text;
                }
                timer1.Start();
            }
            else if (stat == Status.ScanFilesEnd) // сканирование файлов закончено (начинаем обработку)
            {
                // если НЕ режим "только новые", выставляем у всех записей статус "не существует"
                if (!OnlyNew)
                {
                    for (int i = 0; i < Gl.DB.Count; i++)
                        Gl.DB[i].Stat = AMV.Status.NotExist;
                }

                StartParse();
            }
            else if (stat == Status.Load) // обработка файлов
            {
                progressBar1.Value = val;
                Text = "Обработка файлов " + Math.Round(val / (double)progressBar1.Maximum * 100.0).ToString() + "%";
                label1.Text = LocalPathForShow;
                timer1.Start();
            }
            else if (stat == Status.Break || stat == Status.Pause) // обработка файлов в режиме отмены
            {
                Text = "Отмена обработки файлов";
                timer1.Start();
            }
            else if (stat == Status.LoadEnd || stat == Status.BreakEnd) // обработка файлов закончена 
            {
                if (stat == Status.BreakEnd)
                {
                    Gl.DB = oldDB;
                    cntAdd = 0;
                    cntUpdate = 0;
                    cntNotExist = 0;
                    cntDupl = 0;
                }
                else
                {
                    // подсчет не существующих более
                    cntNotExist = 0;
                    Application.DoEvents();
                    for (int i = 0; i < Gl.DB.Count; i++) if (Gl.DB[i].Stat == AMV.Status.NotExist) cntNotExist++;

                    // создание лога
                    BuildLog();

                }

                bClose = true;
                Close();
            }
            cnt++;
        }

        void BuildLog()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("--- " + DateTime.Now.ToString("dd.MM.yyyy HH.mm.ss") + " ---");
            sb.AppendLine();
            sb.AppendLine("Всего найдено файлов " + (cntAdd + cntUpdate + cntDupl).ToString() + ". Из них:");
            sb.AppendLine("\t- Добавлено " + cntAdd.ToString() + " шт.");
            sb.AppendLine("\t- Обновлено " + cntUpdate.ToString() + " шт.");
            sb.AppendLine("\t- Дубликаты " + cntDupl.ToString() + " шт.");
            sb.AppendLine();
            sb.AppendLine("Остались не найденными " + cntNotExist.ToString() + " шт.");
            sb.AppendLine("----------------------------------------------------------");
            sb.AppendLine();
            sb.AppendLine("--- ДОБАВЛЕННЫЕ ---");
            foreach (string s in Log_Add) sb.AppendLine(" - " + s);
            sb.AppendLine("----------------------------------------------------------");
            sb.AppendLine();
            sb.AppendLine("--- ОБНОВЛЕННЫЕ ---");
            foreach (string s in Log_Update) sb.AppendLine(" - " + s);
            sb.AppendLine("----------------------------------------------------------");
            sb.AppendLine();
            sb.AppendLine("--- ДУБЛИКАТЫ ---");
            foreach (KeyValuePair<string, List<string>> dupl in DuplL)
            {
                sb.AppendLine(" - В БАЗЕ: " + dupl.Value[0]);
                for (int i = 1; i < dupl.Value.Count; i++) sb.AppendLine("\t- " + dupl.Value[i]);
                sb.AppendLine();
            }
            sb.AppendLine("----------------------------------------------------------");
            sb.AppendLine();

            File.WriteAllText(Gl.fPathUpdateLog, sb.ToString(), Encoding.UTF8);
        }

        /// <summary>Сканирование домашней директории и заполнение списка всех файлов</summary>
        void ScanFileInTread()
        {
            String[] f = Directory.GetFiles(Gl.Sett.DPathHomeDir, "*", SearchOption.AllDirectories);

            for (int i = 0; i < f.Length; i++)
            {
                if (!Gl.ExtIsMatch(new FileInfo(f[i]).Extension)) continue;

                // если режим "только новые", добавляем только те файлы, которых нет в базе
                if (OnlyNew)
                {
                    int ind = Gl.DB.IndexOfFPath(f[i].Replace(Gl.Sett.DPathHomeDir, ""));
                    if (ind != -1)
                    {
                        continue;
                    }
                }

                fl.Add(f[i]);

                lock (locker)
                {
                    while (true)
                    {
                        if (stat != Status.Pause) break;
                        else Thread.Sleep(100);
                    }
                    if (stat == Status.Break) break;
                }

            }
            lock (locker)
            {
                if (stat != Status.Break) stat = Status.ScanFilesEnd;
                else stat = Status.BreakEnd;
            }
        }

        void FillAmvInTread()
        {
            int Len = 0; lock (fl) Len = fl.Count;
            for (int i = 0; i < Len; i++)
            {
                lock (locker) val++;

                FillAmv(fl[i], OnlyNew);

                // проверка на паузу или досрочный выход
                lock (locker)
                {
                    while (true)
                    {
                        if (stat == Status.Pause) Thread.Sleep(100);
                        else break;
                    }
                    if (stat == Status.Break)
                        break;
                }

            }

            // завершение цикла (по окончанию или досрочному прекращению)
            lock (locker)
            {
                if (stat != Status.Break) stat = Status.LoadEnd;
                else stat = Status.BreakEnd;
            }
            Thread.Sleep(100);
        }

        /// <summary>добавление новой записи или обновление существующей для файла fPath</summary>
        /// <param name="fPath">файл, который необходимо прикрепить к записи</param>
        /// <param name="bOnlyNew">только добавление новых записей</param>
        void FillAmv(string fPath, bool bOnlyNew)
        {
            String localPath = fPath.Replace(Gl.Sett.DPathHomeDir, "");

            // для отображения текущего названия
            lock (LocalPathForShow) { LocalPathForShow = localPath; }

            // если только новые, смотрим есть ли уже такой путь к файлу в базе
            if (bOnlyNew) if (Gl.DB.IndexOfFPath(localPath) != -1) throw new Exception("В режиме 'Только новые' Попытка добавить файл по пути, который уже есть в базе");

            // находим md5
            String md5Len = Gl.GetMd5Len(fPath);

            // смотрим, есть ли уже такая запись в базе
            int ind_InDB;
            ind_InDB = Gl.DB.IndexOfMD5(md5Len);

            // если записи нет, добавляем новую запись
            if (ind_InDB == -1)
            {
                AMV amv;
                String fName = Comm.GetFileName(fPath).Replace("_", " ");
                long FileLen = new FileInfo(fPath).Length;

                amv = ParseFileNameString(fName, localPath, md5Len, FileLen, AMV.Status.Normal);
                amv.Tags.Add(TagForAdd);

                Gl.DB.Add(amv);
                cntAdd++;
                Log_Add.Add(localPath);
            }
            // если такая запись уже есть в базе
            else
            {
                // обновляем ее, если она помечен, как несуществующая
                if (Gl.DB[ind_InDB].Stat == AMV.Status.NotExist)
                {
                    cntUpdate++;
                    Gl.DB[ind_InDB].Stat = AMV.Status.Normal;
                    Gl.DB[ind_InDB].FLocalPath = localPath; // так, как путь мог поменяться, обновляем его
                    Log_Update.Add(localPath);
                }
                // или пишем дубликат, если запись в статусе "вновь существует или норм"  
                else
                {
                    cntDupl++;
                    AddInDuplicate(Gl.DB[ind_InDB].FLocalPath, localPath, md5Len);
                }
            }

        }

        void AddInDuplicate(string fPath, string fPathDupl, string md5Len)
        {
            if (DuplL.ContainsKey(md5Len)) DuplL[md5Len].Add(fPathDupl);
            else DuplL.Add(md5Len, new List<string>() { fPath, fPathDupl });
        }

        AMV ParseFileNameString(string fName, string fPath, string MD5Len, long Len, AMV.Status stat)
        {
            // проверка фразы ".amvnews.ru" и ее удаление
            bool bAmvNews = fName.IndexOf(".amvnews.ru") != -1;
            fName = fName.Replace(".amvnews.ru", "");
            // нахождение индекса начала строки после номера AMVNews, если таковой имеется
            int shAfterNum = -1;
            string[] ssl = fName.Split('.');
            if (ssl.Length > 1)
            {
                if (ssl[0].Length > 4)
                {
                    int n = 0;
                    if (int.TryParse(ssl[0].Substring(ssl[0].Length - 5, 5), out n))
                    {
                        shAfterNum = ssl[0].Length + 1;
                        bAmvNews = true;
                    }
                }
            }

            // создание новой записи по amv
            AMV amv = new AMV(Gl.DB.GetUnicName(fName), fPath, MD5Len, Len, AMV.Status.Normal);

            #region *** парсинг ********************************************

            // отключен
            if (false)
            {
                // [AKROSS]_автор-название
                string keyStr = "[AKROSS]";
                int sh = fName.IndexOf(keyStr);
                if (sh != -1)
                {
                    string s = fName.Substring(sh + keyStr.Length, fName.Length - (sh + keyStr.Length));
                    if (s[0] == ' ' || s[0] == '.') s = fName.Substring(sh + keyStr.Length + 1, fName.Length - (sh + keyStr.Length + 1));
                    if (ParseAuthorAndName(s, '-', amv))
                    {
                        amv.Comment = "участник AKROSS";
                        if (bAmvNews) amv.Comment += "\r\n(взято с amvnews.ru)";
                        return amv;
                    }
                }

                // [AKROSS_Con_год]_автор-название
                // 00000.[AKROSS_Con_yyyy]_автор-название
                // 00000.[AKROSS_Con_yyyy]-название
                // 00000.[AKROSS_Con_yyyy]-автор-название
                // 00000.[AKROSS_Con_yyyy].автор-название
                sh = fName.IndexOf("[AKROSS Con");
                if (sh != -1)
                {
                    string s = fName.Substring(18 + sh, fName.Length - (18 + sh));
                    if (ParseAuthorAndName(s, '-', amv))
                    {
                        string cnv = fName.Substring(1 + sh, 15);
                        amv.Comment = "участник " + cnv;
                        if (sh > 5)
                            if (int.TryParse(fName.Substring(sh - 6, 5), out sh))
                                bAmvNews = true;
                        if (bAmvNews) amv.Comment += "\r\n(взято с amvnews.ru)";
                        return amv;
                    }
                }

                // 00000.автор-название.amvnews.ru
                // 00000.автор-название
                // 00000.название
                if (shAfterNum != -1)
                {
                    string s = fName.Substring(shAfterNum, fName.Length - shAfterNum).Replace('.', ' ');
                    if (ParseAuthorAndName(s, '-', amv))
                    {
                        amv.Comment = "взято с amvnews.ru";
                        return amv;
                    }
                }

                // AMVNews.конвент.yyyy-автор-название
                // AMVNews.конвент.yyyy-название
                sh = fName.IndexOf("AMVNews.");
                if (sh != -1)
                {
                    string[] sL = fName.Substring(sh + 8, fName.Length - (sh + 8)).Split('-');
                    string cnv = sL[0].Replace('.', ' ').Trim();
                    string s = fName.Substring(sh + cnv.Length + 9, fName.Length - (sh + cnv.Length + 9)).Replace('.', ' ');
                    if (ParseAuthorAndName(s, '-', amv))
                    {
                        amv.Comment = "участник " + cnv + "\r\n(взято с amvnews.ru)";
                        return amv;
                    }
                }

                // автор-название.amvnews.ru
                // название.amvnews.ru
                if (bAmvNews)
                {
                    if (ParseAuthorAndName(fName.Replace('.', ' '), '-', amv))
                    {
                        amv.Comment = "взято с amvnews.ru";
                    }
                }
            }
            #endregion *** парсинг ********************************************

            return amv;
        }

        bool ParseAuthorAndName(string str, char splt, AMV amv)
        {
            string[] sl = str.Split(splt);
            if (sl.Length > 1)
            {
                string nm = sl[1]; for (int i = 2; i < sl.Length; i++) nm += splt + sl[i]; nm = nm.Trim();
                string atr = sl[0].Trim();
                if (nm == "" || atr == "") return false;
                amv.Name = Gl.DB.GetUnicName(nm);
                amv.Atr.Add(atr);
                return true;
            }
            else if (sl[0].Trim() != "")
            {
                amv.Name = Gl.DB.GetUnicName(sl[0].Trim());
                return true;
            }
            return false;
        }

        private void FormLoad_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (!bClose)
            {
                e.Cancel = true;
                Status st = stat;
                stat = Status.Pause;
                if (MessageBox.Show("Действительно отменить обновление?", "Отмена обновления", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes)
                    stat = Status.Break;
                else
                    stat = st;
            }

        }

    }

}
